package com.atguigu.yygh.hosp.service.impl;

import com.alibaba.excel.util.CollectionUtils;
import com.alibaba.fastjson.JSONObject;
import com.atguigu.yygh.common.exception.YyghException;
import com.atguigu.yygh.common.result.ResultCodeEnum;
import com.atguigu.yygh.hosp.repository.ScheduleRepository;
import com.atguigu.yygh.hosp.service.DepartmentService;
import com.atguigu.yygh.hosp.service.HospitalService;
import com.atguigu.yygh.hosp.service.ScheduleService;
import com.atguigu.yygh.model.hosp.BookingRule;
import com.atguigu.yygh.model.hosp.Department;
import com.atguigu.yygh.model.hosp.Hospital;
import com.atguigu.yygh.model.hosp.Schedule;
import com.atguigu.yygh.vo.hosp.BookingScheduleRuleVo;
import com.atguigu.yygh.vo.hosp.ScheduleOrderVo;
import com.atguigu.yygh.vo.hosp.ScheduleQueryVo;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.joda.time.DateTime;
import org.joda.time.DateTimeConstants;
import org.joda.time.format.DateTimeFormat;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.*;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author ybl
 * @version 1.9
 * @date 2021/5/18 8:52
 */
@Service
public class ScheduleServiceImpl implements ScheduleService {

  @Autowired
  private ScheduleRepository scheduleRepository;

  @Autowired
  private MongoTemplate mongoTemplate;

  @Autowired
  private HospitalService hospitalService;

  @Autowired
  private DepartmentService departmentService;

  // 添加排班信息
  @Override
  public void save(Map<String, Object> paramMap) {
    // 参数类型转换
    Schedule schedule = JSONObject.parseObject(JSONObject.toJSONString(paramMap), Schedule.class);
    // 根据医院id和排班id 查询排班信息
    Schedule targetSchedule = scheduleRepository.getScheduleByHoscodeAndHosScheduleId(schedule.getHoscode(), schedule.getHosScheduleId());

    if (targetSchedule != null){//修改
      schedule.setUpdateTime(new Date());
      schedule.setStatus(1);
      schedule.setIsDeleted(0);
      scheduleRepository.save(schedule);
    }else { // 保存
      schedule.setCreateTime(new Date());
      schedule.setUpdateTime(new Date());
      schedule.setStatus(1);
      schedule.setIsDeleted(0);
      scheduleRepository.save(schedule);
    }
  }

  // 分页查询schedule
  @Override
  public Page<Schedule> findPageDepartment(int page, int limit, ScheduleQueryVo scheduleQueryVo) {
    Sort sort = Sort.by(Sort.Direction.DESC, "createTime");
//0为第一页
    Pageable pageable = PageRequest.of(page-1, limit, sort);

    Schedule schedule = new Schedule();
    BeanUtils.copyProperties(scheduleQueryVo, schedule);
    schedule.setIsDeleted(0);
    schedule.setStatus(1);

//创建匹配器，即如何使用查询条件
    ExampleMatcher matcher = ExampleMatcher.matching() //构建对象
        .withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING) //改变默认字符串匹配方式：模糊查询
        .withIgnoreCase(true); //改变默认大小写忽略方式：忽略大小写

//创建实例
    Example<Schedule> example = Example.of(schedule, matcher);
    Page<Schedule> pages = scheduleRepository.findAll(example, pageable);
    return pages;
  }

  // 删除排班信息
  @Override
  public void remove(String hoscode, String hosScheduleId) {
    Schedule schedule = scheduleRepository.getScheduleByHoscodeAndHosScheduleId(hoscode, hosScheduleId);
    if (schedule != null){
      scheduleRepository.deleteById(schedule.getId());
    }
  }

  //根据排班id获取排班数据
  @Override
  public Schedule getSchedule(String scheduleId) {
    Schedule schedule = scheduleRepository.findById(scheduleId).get();
    this.packSchedule(schedule);
    return schedule;
  }
  // 修改排班
  @Override
  public void update(Schedule schedule) {
    schedule.setUpdateTime(new Date());
    // id一致就是更新
    scheduleRepository.save(schedule);
  }

  // 根据排班id获取预约下单数据
  @Override
  public ScheduleOrderVo getScheduleOrderVo(String scheduleId) {
    ScheduleOrderVo scheduleOrderVo = new ScheduleOrderVo();
    //排班信息
    Schedule schedule = this.getSchedule(scheduleId);
    if (schedule == null) {
      throw new YyghException(ResultCodeEnum.PARAM_ERROR);
    }
    //获取预约规则信息
    Hospital byHoscode = hospitalService.getByHoscode(schedule.getHoscode());
    if (null == byHoscode) {
      throw new YyghException(ResultCodeEnum.PARAM_ERROR);
    }
    BookingRule bookingRule = byHoscode.getBookingRule();
    if (null == bookingRule) {
      throw new YyghException(ResultCodeEnum.PARAM_ERROR);
    }

    scheduleOrderVo.setHoscode(schedule.getHoscode());
    scheduleOrderVo.setHosname(hospitalService.findHosnameByHoscode(schedule.getHoscode()));
    scheduleOrderVo.setDepcode(schedule.getDepcode());
    scheduleOrderVo.setDepname(departmentService.getDeptName(schedule.getHoscode(), schedule.getDepcode()));
    scheduleOrderVo.setHosScheduleId(schedule.getHosScheduleId());
    scheduleOrderVo.setAvailableNumber(schedule.getAvailableNumber());
    scheduleOrderVo.setTitle(schedule.getTitle());
    scheduleOrderVo.setReserveDate(schedule.getWorkDate());
    scheduleOrderVo.setReserveTime(schedule.getWorkTime());
    scheduleOrderVo.setAmount(schedule.getAmount());

    //退号截止天数（如：就诊前一天为-1，当天为0）
    Integer quitDay = bookingRule.getQuitDay();
    DateTime quitTime = this.getDateTime(new DateTime(schedule.getWorkDate()).plusDays(quitDay).toDate(),
        bookingRule.getQuitTime());
    scheduleOrderVo.setQuitTime(quitTime.toDate());
    //预约开始时间
    DateTime releaseTime = this.getDateTime(new Date(), bookingRule.getReleaseTime());
    scheduleOrderVo.setStartTime(releaseTime.toDate());
    // 预约截止时间
    DateTime endTime = this.getDateTime(new DateTime().plusDays(bookingRule.getCycle()).toDate(), bookingRule.getStopTime());
    scheduleOrderVo.setEndTime(endTime.toDate());
    //当天停止挂号时间
    DateTime stopTime = this.getDateTime(new Date(), bookingRule.getStopTime());
    scheduleOrderVo.setStopTime(stopTime.toDate());

    return scheduleOrderVo;
  }

  //根据hoscode和depcode查询schedule
  @Override
  public Map<String, Object> findByHoscodeAndDepcode(Long page, Long limit, String hoscode, String depcode) {
    Map<String, Object> result = new HashMap<>();

    //1 根据医院编号 和 科室编号 查询
    Criteria criteria = Criteria.where("hoscode").is(hoscode).and("depcode").is(depcode);

    //2 根据工作日期workDate进行分组
    Aggregation aggregation = Aggregation.newAggregation(
        Aggregation.match(criteria),//匹配条件
        Aggregation.group("workDate")//分组字段
            .first("workDate").as("workDate")
            //3 统计号源数量
            .count().as("docCount")
            .sum("reservedNumber").as("reservedNumber")
            .sum("availableNumber").as("availableNumber"),
        //排序
        Aggregation.sort(Sort.Direction.DESC,"workDate"),
        // 实现分页
        Aggregation.skip((page - 1) * limit),
        Aggregation.limit(limit)
    );
    // 调用方法,最终执行
    AggregationResults<BookingScheduleRuleVo> aggregate =
        mongoTemplate.aggregate(aggregation, Schedule.class, BookingScheduleRuleVo.class);
    List<BookingScheduleRuleVo> mappedResults = aggregate.getMappedResults();

    //分组查询的总记录数
    Aggregation aggregation1 = Aggregation.newAggregation(
        Aggregation.match(criteria),
        Aggregation.group("workDate")
    );
    AggregationResults<BookingScheduleRuleVo> aggregate1 =
        mongoTemplate.aggregate(aggregation1, Schedule.class, BookingScheduleRuleVo.class);
    int size = aggregate1.getMappedResults().size();

    //把日期对应星期获取
    for (BookingScheduleRuleVo mappedResult : mappedResults) {
      Date workDate = mappedResult.getWorkDate();
      String dayOfWeek = this.getDayOfWeek(new DateTime(workDate));
      mappedResult.setDayOfWeek(dayOfWeek);
    }

    //获取医院名称
    String hosname = hospitalService.findHosnameByHoscode(hoscode);

    //其他基础数据
    Map<String, String> baseMap = new HashMap<>();
    baseMap.put("hosname",hosname);

    // 封装最终数据
    result.put("bookingScheduleRuleList",mappedResults);
    result.put("total",size);
    result.put("baseMap",baseMap);


    return result;
  }

  //根据医院编号 、科室编号和工作日期，查询排班详细信息
  @Override
  public List<Schedule> getDetailSchedule(String hoscode, String depcode, String workDate) {

    List<Schedule> scheduleList =
        scheduleRepository.findScheduleByHoscodeAndDepcodeAndWorkDate(hoscode, depcode, new DateTime(workDate).toDate());

    //把得到list集合遍历，向设置其他值：医院名称、科室名称、日期对应星期
    scheduleList.stream().forEach(item -> {
      this.packSchedule(item);
    });

    return scheduleList;
  }

  // 获取可预约排班数据
  @Override
  public Map<String, Object> getBookingScheduleRule(Integer page, Integer limit, String hoscode, String depcode) {
    Map<String, Object> result = new HashMap<>();

    //获取预约规则
    Hospital hospital = hospitalService.getByHoscode(hoscode);
    if (hospital == null){
      throw new YyghException(ResultCodeEnum.DATA_ERROR);
    }
    BookingRule bookingRule = hospital.getBookingRule();

    //获取可预约日期分页数据
    IPage iPage = this.getListDate(page,limit,bookingRule);
    //当前页可预约日期
    List<Date> dateList = iPage.getRecords();
    //获取可预约日期科室剩余预约数
    // 封装查询条件
    Criteria criteria = Criteria.where("hoscode").is(hoscode).and("depcode").is(depcode).and("workDate").in(dateList);
    Aggregation agg = Aggregation.newAggregation(
        Aggregation.match(criteria),
        Aggregation.group("workDate") // 分组字段
            .first("workDate").as("workDate")
            .count().as("docCount")//
            .sum("availableNumber").as("availableNumber")//可预约数量
            .sum("reservedNumber").as("reservedNumber")//剩余预约数量
    );
    // 获取聚合结果封装成BookingScheduleRuleVo对象
    AggregationResults<BookingScheduleRuleVo> aggregationResults
        = mongoTemplate.aggregate(agg, Schedule.class, BookingScheduleRuleVo.class);
    // 获取可预约日期的数据集合
    List<BookingScheduleRuleVo> scheduleVoList  = aggregationResults.getMappedResults();
    //获取科室剩余预约数
    //合并数据 将统计数据ScheduleVo根据“安排日期”合并到BookingRuleVo
    Map<Date, BookingScheduleRuleVo> scheduleVoMap = new HashMap<>();
    if (!CollectionUtils.isEmpty(scheduleVoList)){
      scheduleVoMap = scheduleVoList.stream().collect(
          Collectors.toMap(BookingScheduleRuleVo::getWorkDate, x -> x)
      );
    }

    //获取可预约排班规则
    List<BookingScheduleRuleVo> bookingScheduleRuleVoList = new ArrayList<>();
    int i = -1;
    for (Date date : dateList) {
      i++;
      BookingScheduleRuleVo bookingScheduleRuleVo = scheduleVoMap.get(date);
      if (null == bookingScheduleRuleVo) {// 说明当天没有排班医生
        bookingScheduleRuleVo = new BookingScheduleRuleVo();
        //就诊医生人数
        bookingScheduleRuleVo.setDocCount(0);
        //科室剩余预约数  -1表示无号
        bookingScheduleRuleVo.setAvailableNumber(-1);
      }
      // 可预约日期
      bookingScheduleRuleVo.setWorkDate(date);
      bookingScheduleRuleVo.setWorkDateMd(date);
      //计算当前预约日期为周几
      String dayOfWeek = this.getDayOfWeek(new DateTime(date));
      bookingScheduleRuleVo.setDayOfWeek(dayOfWeek);
      //最后一页最后一条记录为即将预约   状态 0：正常 1：即将放号 -1：当天已停止挂号
      if(i == dateList.size()-1 && page == iPage.getPages()) {
        bookingScheduleRuleVo.setStatus(1);
      } else {
        bookingScheduleRuleVo.setStatus(0);
      }
      //当天预约如果过了停号时间， 不能预约
      if (i ==0 && page == 1) {
        DateTime dateTime = this.getDateTime(new Date(), bookingRule.getStopTime());
        if (dateTime.isBeforeNow()) {
          // 停止预约
          bookingScheduleRuleVo.setStatus(-1);
        }
      }
      bookingScheduleRuleVoList.add(bookingScheduleRuleVo);
    }

    //可预约日期规则数据
    result.put("bookingScheduleList", bookingScheduleRuleVoList);
    result.put("total", iPage.getTotal());
    //其他基础数据
    Map<String, String> baseMap = new HashMap<>();
    //医院名称
    baseMap.put("hosname", hospitalService.findHosnameByHoscode(hoscode));
    //科室
    Department department =departmentService.getDepartment(hoscode, depcode);
    //大科室名称
    baseMap.put("bigname", department.getBigname());
    //科室名称
    baseMap.put("depname", department.getDepname());
//月
    baseMap.put("workDateString", new DateTime().toString("yyyy年MM月"));
//放号时间
    baseMap.put("releaseTime", bookingRule.getReleaseTime());
//停号时间
    baseMap.put("stopTime", bookingRule.getStopTime());
    result.put("baseMap", baseMap);

    return result;
  }

  //获取可预约日期分页数据
  private IPage getListDate(Integer page, Integer limit, BookingRule bookingRule) {
    //当天放号时间
    DateTime releaseTime = this.getDateTime(new Date(),bookingRule.getReleaseTime());
//预约周期 如:10天
    Integer cycle = bookingRule.getCycle();

    //如果当天放号时间已过，则预约周期后一天为即将放号时间，周期加1
    cycle = releaseTime.isBeforeNow() ? cycle +1 : cycle;

    //可预约所有日期，最后一天显示即将放号倒计时
    List<Date> dateList = new ArrayList<>();
    // for (int i = cycle - bookingRule.getCycle(); i < cycle; i++) {
    for (int i = 0; i < cycle; i++) {
//计算当前预约日期
      DateTime curDateTime = new DateTime().plusDays(i);
      String dateString = curDateTime.toString("yyyy-MM-dd");
      dateList.add(new DateTime(dateString).toDate());
    }

    //日期分页，由于预约周期不一样，页面一排最多显示7天数据，多了就要分页显示
    List<Date> pageDateList = new ArrayList<>();
    int start = (page-1)*limit;
    int end = (page-1)*limit+limit;
    // 最后一页如果不够7条数据 把end改为数组的长度
    if (end>dateList.size()) end = dateList.size();
    // 封装分页的日期
    for (int i = start;i<end;i++){
      pageDateList.add(dateList.get(i));
    }
    IPage iPage = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(page,7,dateList.size());
    iPage.setRecords(pageDateList);
    return iPage;
  }

  //当天放号时间
  //将Date日期（yyyy-MM-dd HH:mm）转换为DateTime
  private DateTime getDateTime(Date date, String releaseTime) {
    // 拼接字符串 添加上日期
    String datetimeString = new DateTime(date).toString("yyyy-MM-dd")+" "+ releaseTime;
    // 把字符串转换为DateTime类型
    return DateTimeFormat.forPattern("yyyy-MM-dd HH:mm").parseDateTime(datetimeString);
  }

  // 封装排班详情其他值 医院名称、科室名称、日期对应星期
  private void packSchedule(Schedule schedule) {
    //设置医院名称
    schedule.getParam().put("hosname", hospitalService.findHosnameByHoscode(schedule.getHoscode()));
    //设置科室名称
    schedule.getParam().put("depname", departmentService.getDeptName(schedule.getHoscode(), schedule.getDepcode()));
    //设置日期对应星期
    schedule.getParam().put("dayOfWeek", this.getDayOfWeek(new DateTime(schedule.getWorkDate())));
  }

    /**
     * 根据日期获取周几数据
     * @param dateTime
     * @return
     */
  private String getDayOfWeek(DateTime dateTime) {
    String dayOfWeek = "";
    switch (dateTime.getDayOfWeek()) {
      case DateTimeConstants.SUNDAY:
        dayOfWeek = "周日";
        break;
      case DateTimeConstants.MONDAY:
        dayOfWeek = "周一";
        break;
      case DateTimeConstants.TUESDAY:
        dayOfWeek = "周二";
        break;
      case DateTimeConstants.WEDNESDAY:
        dayOfWeek = "周三";
        break;
      case DateTimeConstants.THURSDAY:
        dayOfWeek = "周四";
        break;
      case DateTimeConstants.FRIDAY:
        dayOfWeek = "周五";
        break;
      case DateTimeConstants.SATURDAY:
        dayOfWeek = "周六";
      default:
        break;
    }
    return dayOfWeek;
  }

}
